package com.zc.auth.security.core.access;

import com.zc.auth.security.core.access.meta.UrlRoleMetaDataManager;
import com.zc.auth.security.core.authentication.AnonymousAuthenticationToken;
import com.zc.auth.security.core.exception.AnonymousAccessDeniedException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.Map;

/**
 * 角色url访问决策处理
 */
public class MonoRoleUrlAccessDecisionManager implements ReactiveAuthorizationManager<ServerWebExchange> {

    private UrlRoleMetaDataManager urlRoleMetaDataManager;

    public MonoRoleUrlAccessDecisionManager(UrlRoleMetaDataManager urlRoleMetaDataManager) {
        this.urlRoleMetaDataManager = urlRoleMetaDataManager;
    }

    public void setUrlRoleMetaDataManager(UrlRoleMetaDataManager urlRoleMetaDataManager) {
        this.urlRoleMetaDataManager = urlRoleMetaDataManager;
    }

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, ServerWebExchange exchange) {
        Map<String, ? extends Collection<String>> map = urlRoleMetaDataManager.getUrlsAndRolesMap();
        Collection<String> roles = urlRoleMetaDataManager.getAccessibleRoles(exchange.getRequest().getURI().getPath(), map);
        if (CollectionUtils.isEmpty(roles)) {
            return Mono.just(new AuthorizationDecision(true));
        }
        return authenticationMono
                .filter(a -> a.isAuthenticated() && !(a instanceof AnonymousAuthenticationToken))
                .switchIfEmpty(Mono.error(new AnonymousAccessDeniedException("不能匿名访问，请登录！")))
                .flatMapIterable(a -> a.getAuthorities())
                .map(g -> g.getAuthority())
                .any(a -> roles.contains(a))
                .map(hasAuthority -> new AuthorizationDecision(hasAuthority));
    }

    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, ServerWebExchange exchange) {
        return this.check(authentication, exchange)
                .filter(decision -> decision.isGranted())
                .switchIfEmpty(Mono.error(new AccessDeniedException("无权限！")))
                .flatMap(decision -> Mono.empty());
    }
}
